/**
 * This file is part of JNotifyOSD.
 *
 * JNotifyOSD is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JNotifyOSD is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JNotifyOSD. If not, see <http://www.gnu.org/licenses/>.
 */

// Win32.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "Win32.h"
#include "IcoLoader.h"

static
	void ThrowAnException(JNIEnv* env)
{
	DWORD err = GetLastError();
	char msg[200];
	if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, err, 0, (LPSTR)&msg, 200, NULL) == 0)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), NULL);
	}
	else
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), msg);
	}
}

//NotificationService API
extern HINSTANCE myInstance;

typedef struct
{
	HWND mainWindow;
	NOTIFYICONDATAA nid;
	jstring appName;
} NotificationServiceData;

static ATOM mClass = 0;
static NotificationServiceData* data = NULL;

static
LRESULT CALLBACK NotificationServiceWndProc(_In_  HWND hwnd,
							_In_  UINT uMsg,
							_In_  WPARAM wParam,
							_In_  LPARAM lParam)
{
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WIN32_API void Java_notifyosd_NativeNotificationService_nativeInit(
	JNIEnv* env, jobject obj, jstring app_name)
{
	/*
	 * nativeInit is called by a synchronized method, and is thus serialized
	 */
	if (mClass == 0)
	{
	
		//Register Window class
		WNDCLASSA mClassData;
		ZeroMemory(&mClassData, sizeof(mClassData));
		mClassData.lpfnWndProc = NotificationServiceWndProc;
		mClassData.hInstance = myInstance;
		mClassData.lpszClassName = "NotificationServiceClass";

		mClass = RegisterClassA(&mClassData);
		if (mClass == 0)
		{
			ThrowAnException(env);
			return;
		}
	}

	const char* c_app_name = env->GetStringUTFChars(app_name, NULL);
	data = new NotificationServiceData();
	
	data->mainWindow = CreateWindowA((LPCSTR)MAKELONG(mClass, 0),
			c_app_name, 0, 0, 0, 0, 0, NULL, NULL, myInstance, 0);
	env->ReleaseStringUTFChars(app_name, c_app_name);

	if (data->mainWindow == NULL)
	{
		ThrowAnException(env);
		delete data;
		return;
	}

	data->nid.cbSize = sizeof(NOTIFYICONDATAA);
	data->nid.hWnd = data->mainWindow;
	data->nid.uID = 1;
	data->nid.dwState = NIS_HIDDEN;
	data->nid.dwStateMask = NIS_HIDDEN;
	data->nid.uVersion = NOTIFYICON_VERSION;
	
	if (Shell_NotifyIconA(NIM_ADD, &data->nid) == FALSE)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), NULL);
		delete data;
		return;
	}

	if (Shell_NotifyIconA(NIM_SETVERSION, &data->nid) == FALSE)
	{
		Java_notifyosd_NativeNotificationService_nativeUninit(env, obj);
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "Unsupported API Version");
		return;
	}

	data->appName = reinterpret_cast<jstring>(env->NewGlobalRef(app_name));
}

WIN32_API void Java_notifyosd_NativeNotificationService_nativeUninit(JNIEnv* env, jobject obj)
{
	FreeIcoLoaderMemory();
	env->DeleteGlobalRef(data->appName);
	Shell_NotifyIconA(NIM_DELETE, &data->nid);
	delete data;
	data = NULL;
}

WIN32_API jboolean Java_notifyosd_NativeNotificationService_nativeIsInitted(JNIEnv* env, jobject obj)
{
	return data != NULL;
}

WIN32_API jobjectArray Java_notifyosd_NativeNotificationService_nativeGetServerCaps(JNIEnv* env, jobject obj)
{
	/*
	 * TODO return something meaningful
	 * maybe a Win32 equivalent of LibNM capabilities
	 */
	return env->NewObjectArray(0, env->FindClass("java/lang/String"), NULL);
}

WIN32_API jobjectArray Java_notifyosd_NativeNotificationService_nativeGetServerInfo(JNIEnv* env, jobject obj)
{
	jclass stringClass = env->FindClass("java/lang/String");
	//String class should always exist in a sane environment

	jobjectArray res = env->NewObjectArray(4, stringClass, NULL);
	if (res == NULL)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), NULL);
		return NULL;
	}

	/*
	 * Returned array is
	 * 0: Product name
	 * 1: Vendor
	 * 2: Version
	 * 3: Specification version
	 */
	env->SetObjectArrayElement(res, 0, env->NewStringUTF("Windows Notification Tray"));
	env->SetObjectArrayElement(res, 1, env->NewStringUTF("Microsoft"));
	
	DWORD winVers = GetVersion();
	char winVersString[32];
	_snprintf(winVersString, 32, "%d.%d", (DWORD)(LOBYTE(LOWORD(winVers))), (DWORD)(HIBYTE(LOWORD(winVers))));
	env->SetObjectArrayElement(res, 2, env->NewStringUTF(winVersString));
	
	env->SetObjectArrayElement(res, 3, env->NewStringUTF("N.A."));

	return res;
}

WIN32_API jstring Java_notifyosd_NativeNotificationService_nativeGetAppName(JNIEnv* env, jobject obj)
{
	if (!Java_notifyosd_NativeNotificationService_nativeIsInitted(env, obj))
	{
		env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), "The service has not been initialized yet");
		return NULL;
	}

	return data->appName;
}

//Notification API

WIN32_API void Java_notifyosd_NativeNotification_nativeInit(
	JNIEnv* env,
	jobject obj,
	jstring summary,
	jstring body,
	jstring icon)
{
	//Nothing to do here
}

WIN32_API void Java_notifyosd_NativeNotification_nativeDestroy(JNIEnv* env, jobject obj)
{
	//Nothing to do here
}

WIN32_API void Java_notifyosd_NativeNotification_nativeUpdate(
	JNIEnv* env,
	jobject obj,
	jstring summary,
	jstring body,
	jstring icon)
{
	//Nothing to do here
}

static bool TryLoadIcon(JNIEnv* env, const char *iPath, HICON* icoOut)
{
	*icoOut = ExtractIconA(myInstance, iPath, 0);
	if (icoOut == NULL)
	{
		//TODO use to more meaningful IOExceptions
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "File not found");
		return false;
	}
	return true;
}

static bool GetIcon(JNIEnv* env, jobject obj, HICON* icoOut)
{
	jfieldID iconPathID = env->GetFieldID(env->GetObjectClass(obj), "icon", "Ljava/lang/String;");
	if (iconPathID == NULL)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "Field 'icon' not found");
		return false;
	} 

	jstring iconPath = reinterpret_cast<jstring>(env->GetObjectField(obj, iconPathID));
	if (iconPath == NULL)
	{
		*icoOut = NULL;
		return true;
	}

	jboolean isCopy;
	const char* iconPathCstr = env->GetStringUTFChars(iconPath, &isCopy);
	if (iconPathCstr == NULL)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "Field 'icon' read failed");
		return false;
	}

	int iPathLen = env->GetStringUTFLength(iconPath);
	bool res;

	if (!_strcmpi(iconPathCstr + iPathLen - 4, ".ico"))
	{
		res = TryLoadIcon(env, iconPathCstr, icoOut);
	}
	else if (!IconFromImageFile(iconPathCstr, icoOut))
	{
		res = true;
	}
	else
	{
		env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), "Icon file not supported");
		res = false;
	}

	env->ReleaseStringUTFChars(iconPath, iconPathCstr);
	return res;
}

WIN32_API void Java_notifyosd_NativeNotification_nativeShow(JNIEnv* env, jobject obj)
{
	jfieldID fid;
	jclass objClass = env->GetObjectClass(obj);
	jstring summary, body;
	NOTIFYICONDATAA nid = data->nid;

	//Load the title
	fid = env->GetFieldID(objClass, "title", "Ljava/lang/String;");
	if (fid == NULL)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "Field 'title' not found");
		return;
	}
	summary = reinterpret_cast<jstring>(env->GetObjectField(obj, fid));

	//Load the body
	fid = env->GetFieldID(objClass, "body", "Ljava/lang/String;");
	if (fid == NULL)
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "Field 'body' not found");
		return;
	}
	body = reinterpret_cast<jstring>(env->GetObjectField(obj, fid));

	//Set title and body
	jboolean copiedTitle, copiedBody;
	//XXX is this null-terminated?
	env->GetStringUTFRegion(summary, 0, min(env->GetStringUTFLength(summary), sizeof(nid.szInfoTitle)), nid.szInfoTitle);
	env->GetStringUTFRegion(body, 0, min(env->GetStringUTFLength(body), sizeof(nid.szInfo)), nid.szInfo);

	//Load the icon
	HICON hIcon;
	if (!GetIcon(env, obj, &hIcon)) return;	
	nid.hBalloonIcon = hIcon;

	nid.uTimeout = 0;
	nid.uFlags = NIF_INFO;
	nid.dwInfoFlags = (hIcon) ? (NIIF_USER | NIIF_LARGE_ICON) : NIIF_NONE;
	if (!Shell_NotifyIconA(NIM_MODIFY, &nid))
	{
		env->ThrowNew(env->FindClass("java/lang/RuntimeException"), "Unable to show the OSD");
	}

	if (hIcon != NULL)
	{
		DestroyIcon(hIcon);
	}
}

WIN32_API void Java_notifyosd_NativeNotification_nativeSetTimeout(
	JNIEnv* env, jobject obj, jint timeout)
{
	//TODO implement
}

WIN32_API void Java_notifyosd_NativeNotification_nativeSetCategory(
	JNIEnv* env, jobject obj, jstring category)
{
	//TODO implement
}

WIN32_API jint Java_notifyosd_NativeNotification_getLowPrio(JNIEnv* env, jobject obj)
{
	return 0;
}

WIN32_API jint Java_notifyosd_NativeNotification_getNorPrio(JNIEnv* env, jobject obj)
{
	return 1;
}

WIN32_API jint Java_notifyosd_NativeNotification_getCriPrio(JNIEnv* env, jobject obj)
{
	return 2;
}

WIN32_API void Java_notifyosd_NativeNotification_nativeSetUrgency(
	JNIEnv* env, jobject obj, jint urgency)
{
	//TODO implement
}

WIN32_API void Java_notifyosd_NativeNotification_nativeHide(
	JNIEnv* env, jobject obj)
{
	//TODO implement
}